/**
 * $Revision: 1.0 $
 * $Author: Geln Yang $
 * $Date: Jun 24, 2009 9:21:52 AM $
 *
 * Author: Eric Yang
 * Date  : Jun 24, 2009 9:21:52 AM
 *
 */
package c4j.cvs.patch;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import c4j.date.DateUtil;
import c4j.file.FileUtils;
import c4j.file.ITextFileReader;
import c4j.file.ITextFileWriter;
import c4j.string.StringUtils;

/**
 * @author Eric Yang
 * @version 1.0
 */
public class Patcher {

	public static final Log logger = LogFactory.getLog(Patcher.class);

	public static final String JAVA_COMPILE_DIR = "java_compile_dir";

	public static final String JAVA_DIR_1 = "java_dir_1";

	public static final String JAVA_DIR_2 = "java_dir_2";

	public static final String PROP_DIR_1 = "prop_dir_1";

	public static final String PROP_DIR_2 = "prop_dir_2";

	public static final String CONF_DIR_1 = "conf_dir_1";

	public static final String CONF_DIR_2 = "conf_dir_2";

	public static final String JSP_DIR = "jsp_dir";

	protected static final Set<String> FILTER_FILE_SET;

	protected static final String yyyy_MM_dd_HH_mm_ss = "yyyy-MM-dd HH:mm:ss";

	protected static final String yyyy__MM__dd = "yyyy/MM/dd";
	static {
		FILTER_FILE_SET = new HashSet<String>();
		FILTER_FILE_SET.add("build.xml");
		FILTER_FILE_SET.add("build.properties");
		FILTER_FILE_SET.add("log4j.properties");
	}

	public static void patch(String cvsReportFilePath, String patchDir, Map<String, String> parameters) throws IOException {
		List<CVSRecord> records = listModifiedFiles(cvsReportFilePath);
		patch(records, patchDir, parameters);
	}

	public static void patch(List<CVSRecord> records, String patchDir, Map<String, String> parameters) throws IOException {
		patchDir = FileUtils.initDirPath(patchDir);
		List<CVSRecord> filterRecords = new ArrayList<CVSRecord>();
		for (int i = 0; i < records.size(); i++) {
			CVSRecord record = records.get(i);
			String filePath = record.getFilePath();
			String destFilePath = filePath.substring(filePath.indexOf(File.separator) + 1, filePath.length());
			destFilePath = patchDir + destFilePath;
			try {
				FileUtils.copyTo(filePath, destFilePath);
			} catch (FileNotFoundException e) {
				logger.error(e.getMessage());
				filterRecords.add(record);
			}
		}

		records.removeAll(filterRecords);

		createPatchFiles(records, patchDir, parameters);

		createReleaseNote(records, patchDir);

		createUpdateList(records, filterRecords, patchDir);
	}

	public static void createUpdateList(List<CVSRecord> records, List<CVSRecord> filterRecords, String patchDir) throws IOException {
		String updateListPath = patchDir + "Update List " + DateUtil.getTodayDateValue() + ".html";
		StringBuffer html = new StringBuffer();
		html.append("<html><head><title>Update List " + new Date().toString() + "</title></head><body><table><tbody>");
		html.append("<tr>");
		html.append("<th>No.</th>");
		html.append("<th>Program</th>");
		html.append("<th>Change</th>");
		html.append("<th>Date</th>");
		html.append("<th>Size</th>");
		html.append("<th>Path</th>");
		html.append("<th>Note1</th>");
		html.append("<th>Note2</th>");
		html.append("</tr>");

		boolean isDeletedRecords = false;
		String defaultStatus = "U";
		createUpdateListContent(html, records, defaultStatus, isDeletedRecords);

		isDeletedRecords = true;
		defaultStatus = "D";
		createUpdateListContent(html, filterRecords, defaultStatus, isDeletedRecords);

		html.append("</tbody></table></body></html>");

		final String htmlContent = html.toString();

		FileUtils.writeBufferedTextFile(updateListPath, FileUtils.DEFAULT_CHARSET, new ITextFileWriter() {
			public void write(BufferedWriter writer) throws IOException {
				writer.write(htmlContent);
			}
		});
	}

	private static void createUpdateListContent(StringBuffer html, List<CVSRecord> records, String defaultStatus, boolean isDeletedRecords) throws IOException {
		for (int i = 0; i < records.size(); i++) {
			CVSRecord record = records.get(i);
			String filePath = record.getFilePath();
			String fileName = FileUtils.getFileName(filePath);
			String fileDir = FileUtils.getFileDir(filePath);

			String status = defaultStatus;
			if (!isDeletedRecords && "1.1".equals(record.getRevision())) {
				status = "A";
			}

			SimpleDateFormat dateFormat = new SimpleDateFormat(yyyy_MM_dd_HH_mm_ss);
			SimpleDateFormat parserDateFormat = new SimpleDateFormat(yyyy__MM__dd);
			Date date = null;
			try {
				date = dateFormat.parse(record.getDate());
			} catch (ParseException e) {
				logger.error(e.getMessage(), e);
			}
			String dateValue = parserDateFormat.format(date);
			File file = new File(filePath);
			BigDecimal fileSize = new BigDecimal("0.0");
			try {
				fileSize = new BigDecimal(new FileInputStream(file).available() + "");
				fileSize = fileSize.divide(new BigDecimal("1024")).setScale(3, BigDecimal.ROUND_HALF_UP);
			} catch (FileNotFoundException e) {
				logger.error(e.getMessage(), e);
			}
			html.append("<tr>");
			html.append("<td>" + (i + 1) + "</td>");
			html.append("<td>" + fileName + "</td>");
			html.append("<td>" + status + "</td>");
			html.append("<td>" + dateValue + "</td>");
			html.append("<td>" + fileSize + "KBytes</td>");
			html.append("<td>" + fileDir + "</td>");
			html.append("<td> </td>");
			html.append("<td> </td>");
			html.append("</tr>");
		}
	}

	public static void createReleaseNote(List<CVSRecord> records, String patchDir) throws IOException {
		String releaseNotePath = patchDir + "Release Note " + DateUtil.getTodayDateValue() + ".html";
		StringBuffer html = new StringBuffer();
		html.append("<html><head><title>Release Note " + new Date().toString() + "</title></head><body><table><tbody>");
		html.append("<tr>");
		html.append("<th>No.</th>");
		html.append("<th>Function</th>");
		html.append("<th>Type</th>");
		html.append("<th>File</th>");
		html.append("</tr>");

		for (int i = 0; i < records.size(); i++) {
			CVSRecord record = records.get(i);
			String filePath = record.getFilePath();
			String fileName = FileUtils.getFileName(filePath);
			String fileSuffix = FileUtils.getFileNameSuffix(fileName);
			String type = "";
			if (".jsp".equals(fileSuffix) || ".js".equals(fileSuffix) || ".jpg".equals(fileSuffix)) {
				type = "Page display";
			} else if (".java".equals(fileSuffix)) {
				type = "Java code";
			} else if (".properties".equals(fileSuffix)) {
				type = "Property";
			} else if (fileSuffix.toLowerCase().startsWith("applicationcontext")) {
				type = "Spring configuration";
			} else if (fileSuffix.toLowerCase().endsWith(".hbm.xml")) {
				type = "Hibernate configuration";
			} else if (".wfl".equals(fileSuffix) || ".ftl".equals(fileSuffix)) {
				type = "Template";
			} else if (".xml".equals(fileSuffix) || ".conf".equals(fileSuffix)) {
				type = "System configuration";
			}
			html.append("<tr>");
			html.append("<td>" + (i + 1) + "</td>");
			html.append("<td>update</td>");
			html.append("<td>" + type + "</td>");
			html.append("<td>" + fileName + "</td>");
			html.append("</tr>");

		}

		html.append("</tbody></table></body></html>");

		final String htmlContent = html.toString();
		FileUtils.writeBufferedTextFile(releaseNotePath, FileUtils.DEFAULT_CHARSET, new ITextFileWriter() {
			public void write(BufferedWriter writer) throws IOException {
				writer.write(htmlContent);
			}
		});
	}

	public static List<CVSRecord> listModifiedFiles(String cvsReportFilePath) throws IOException {
		final Set<String> filePathSet = new HashSet<String>();
		final List<CVSRecord> records = new ArrayList<CVSRecord>();

		FileUtils.readBufferedTextFile(cvsReportFilePath, new ITextFileReader() {

			public void read(BufferedReader reader) throws IOException {
				String line = reader.readLine();
				while (line != null) {
					String[] values = line.split(";");
					String filePath = values[3].trim();

					if (filePath.indexOf(File.separator) == -1 || filePath.toLowerCase().matches(".*\\\\test\\\\.*")) {
						line = reader.readLine();
						continue;
					}

					String fileName = FileUtils.getFileName(filePath);
					if (fileName.toLowerCase().startsWith("test") || fileName.toLowerCase().endsWith(".sql") || FILTER_FILE_SET.contains(fileName)) {
						line = reader.readLine();
						continue;
					}

					if (!filePathSet.contains(filePath)) {
						CVSRecord record = new CVSRecord();
						record.setDate(values[0].trim());
						record.setAuthor(values[1].trim());
						record.setMessage(values[2].trim());
						record.setFilePath(values[3].trim());
						record.setRevision(values[4].trim());

						records.add(record);

						filePathSet.add(filePath);
					}

					line = reader.readLine();
				}
			}

		});

		Collections.sort(records, new Comparator<CVSRecord>() {
			public int compare(CVSRecord r1, CVSRecord r2) {
				String f1 = r1.getFilePath();
				String f2 = r2.getFilePath();
				String s1 = FileUtils.getFileNameSuffix(f1);
				String s2 = FileUtils.getFileNameSuffix(f2);
				int result = s1.compareTo(s2);
				return result == 0 ? f1.compareTo(f2) : result;
			}
		});

		return records;
	}

	public static void createPatchFiles(List<CVSRecord> records, String patchDir, Map<String, String> parameters) throws IOException {
		StringBuffer html = new StringBuffer();
		StringBuffer batBuffer1 = new StringBuffer();
		StringBuffer batBuffer2 = new StringBuffer();
		html.append("<html><head><title>Release Note Detail " + new Date().toString() + "</title></head><body><table><tbody>");
		html.append("<tr>");
		html.append("<th>No.</th>");
		html.append("<th>Program</th>");
		html.append("<th>Program Path</th>");
		html.append("<th>Source Code Path</th>");
		html.append("<th>Class path after Compiling</th>");
		html.append("<th>Class path(After Compiling)</th>");
		html.append("</tr>");

		String javaCompileDir = FileUtils.initDirPath(parameters.get(JAVA_COMPILE_DIR));
		String javaDir1 = FileUtils.initDirPath(parameters.get(JAVA_DIR_1));
		String javaDir2 = parameters.get(JAVA_DIR_2);
		if (StringUtils.isNotBlankOrNull(javaDir2))
			javaDir2 = FileUtils.initDirPath(javaDir2);
		String propDir1 = FileUtils.initDirPath(parameters.get(PROP_DIR_1));
		String propDir2 = parameters.get(PROP_DIR_2);
		if (StringUtils.isNotBlankOrNull(propDir2))
			propDir2 = FileUtils.initDirPath(propDir2);
		String confDir1 = FileUtils.initDirPath(parameters.get(CONF_DIR_1));
		String confDir2 = parameters.get(CONF_DIR_2);
		if (StringUtils.isNotBlankOrNull(confDir2))
			confDir2 = FileUtils.initDirPath(confDir2);

		String jspDir = parameters.get(JSP_DIR);
		int index = 1;
		for (int i = 0; i < records.size(); i++) {
			StringBuffer lineContent = new StringBuffer();
			CVSRecord record = records.get(i);
			String filePath = record.getFilePath();
			String fileDir = FileUtils.getFileDir(filePath);
			String fileName = FileUtils.getFileName(filePath);
			String fileSuffix = FileUtils.getFileNameSuffix(fileName);
			lineContent.append("<tr>");
			lineContent.append("<td>" + index + "</td>");
			lineContent.append("<td>Change</td>");
			lineContent.append("<td>" + fileName + "</td>");
			lineContent.append("<td>" + fileDir + "</td>");
			String javaCompilePath = "";
			String outPath1 = "";
			String outPath2 = "";
			String batCommand1 = "";
			String batCommand2 = "";
			try {
				if (".jsp".equals(fileSuffix) || ".js".equals(fileSuffix) || ".jpg".equals(fileSuffix) || ".gif".equals(fileSuffix)) {
					String jspSubPath = getJSPSubPath(filePath);
					String jspOutputPath = jspDir + jspSubPath + fileName;
					outPath1 += jspOutputPath + "<br/>";
					batCommand1 += "copy /y \"" + filePath + "\" \"" + jspOutputPath + "\"\r\n";

				} else if (".java".equals(fileSuffix)) {
					String fileNamePreffix = FileUtils.getFileNamePrefix(fileName);
					String javaPackagePath = getJavaPackagePath(filePath);
					String compileDir = javaCompileDir + javaPackagePath;
					String[] classFilenames = FileUtils.listFilenames(compileDir, fileNamePreffix + "(\\$\\d+)?\\.class");
					for (int j = 0; j < classFilenames.length; j++) {
						javaCompilePath += compileDir + classFilenames[j] + "<br/>";
						String javaOutPath1 = javaDir1 + javaPackagePath + classFilenames[j];
						String classPath = javaCompileDir + javaPackagePath + classFilenames[j];
						outPath1 += javaOutPath1 + "<br/>";
						batCommand1 += "copy /y \"" + classPath + "\" \"" + javaOutPath1 + "\"\r\n";
						if (StringUtils.isNotBlankOrNull(javaDir2)) {
							String javaOutPath2 = javaDir2 + javaPackagePath + classFilenames[j];
							outPath2 += javaOutPath2 + "<br/>";
							batCommand2 += "copy /y \"" + classPath + "\" \"" + javaOutPath2 + "\"\r\n";
						}
					}
				} else if (".properties".equals(fileSuffix)) {
					String propSubPath = getConfSubPath(filePath);
					String propOutputPath1 = propDir1 + propSubPath + fileName;
					outPath1 += propOutputPath1 + "<br/>";
					batCommand1 += "copy /y \"" + filePath + "\" \"" + propOutputPath1 + "\"\r\n";
					if (StringUtils.isNotBlankOrNull(propDir2)) {
						String propOutputPath2 = propDir2 + propSubPath + fileName;
						outPath2 += propOutputPath2 + "<br/>";
						batCommand2 += "copy /y \"" + filePath + "\" \"" + propOutputPath2 + "\"\r\n";
					}
				} else if (".xml".equals(fileSuffix) || ".wfl".equals(fileSuffix) || ".ftl".equals(fileSuffix) || ".conf".equals(fileSuffix)) {
					String propSubPath = getConfSubPath(filePath);
					if (fileName.endsWith(".hbm.xml")) {
						String fileOutputPath1 = javaDir1 + propSubPath + fileName;
						outPath1 += fileOutputPath1 + "<br/>";
						batCommand1 += "copy /y \"" + filePath + "\" \"" + fileOutputPath1 + "\"\r\n";
						if (StringUtils.isNotBlankOrNull(javaDir2)) {
							String fileOutputPath2 = javaDir2 + propSubPath + fileName;
							outPath2 += fileOutputPath2 + "<br/>";
							batCommand2 += "copy /y \"" + filePath + "\" \"" + fileOutputPath2 + "\"\r\n";
						}
					} else {
						String fileOutputPath1 = confDir1 + propSubPath + fileName;
						outPath1 += fileOutputPath1 + "<br/>";
						batCommand1 += "copy /y \"" + filePath + "\" \"" + fileOutputPath1 + "\"\r\n";
						if (StringUtils.isNotBlankOrNull(confDir2)) {
							String fileOutputPath2 = confDir2 + propSubPath + fileName;
							outPath2 += fileOutputPath2 + "<br/>";
							batCommand2 += "copy /y \"" + filePath + "\" \"" + fileOutputPath2 + "\"\r\n";
						}
					}
				}
			} catch (Exception e) {
				logger.error(e.getMessage(), e);
				continue;
			}
			batBuffer1.append(batCommand1 + "\r\n");
			batBuffer2.append(batCommand2 + "\r\n");
			lineContent.append("<td>" + javaCompilePath + "</td>");
			lineContent.append("<td>" + outPath1 + "<br/>" + outPath2 + "</td>");
			lineContent.append("</tr>");

			html.append(lineContent);
			index++;
		}
		html.append("</tbody></table></body></html>");

		final String htmlContent = html.toString();
		final String batContet1 = batBuffer1.toString();
		final String batContet2 = batBuffer2.toString();
		patchDir = FileUtils.initDirPath(patchDir);
		String destHTMLFilePath = patchDir + "Release Note " + DateUtil.getTodayDateValue() + "(Detail).html";
		String destBatFilePath1 = patchDir + "PATCH_" + DateUtil.getTodayDateValue() + "_1.bat";
		String destBatFilePath2 = patchDir + "PATCH_" + DateUtil.getTodayDateValue() + "_2.bat";

		FileUtils.writeBufferedTextFile(destHTMLFilePath, FileUtils.DEFAULT_CHARSET, new ITextFileWriter() {
			public void write(BufferedWriter writer) throws IOException {
				writer.write(htmlContent);
			}
		});
		FileUtils.writeBufferedTextFile(destBatFilePath1, FileUtils.DEFAULT_CHARSET, new ITextFileWriter() {
			public void write(BufferedWriter writer) throws IOException {
				writer.write(batContet1);
			}
		});
		FileUtils.writeBufferedTextFile(destBatFilePath2, FileUtils.DEFAULT_CHARSET, new ITextFileWriter() {
			public void write(BufferedWriter writer) throws IOException {
				writer.write(batContet2);
			}
		});
	}

	private static String getJSPSubPath(String filePath) {
		int start = filePath.indexOf(File.separator + "webapps" + File.separator);
		int end = filePath.lastIndexOf(File.separator);
		if (start != -1 && end != -1) {
			start = start + 9;
			return filePath.substring(start, end + 1);
		}
		return "";
	}

	private static String getConfSubPath(String filePath) {
		int start = filePath.indexOf(File.separator + "conf" + File.separator);
		if (start == -1)
			start = filePath.indexOf(File.separator + "java" + File.separator);
		int end = filePath.lastIndexOf(File.separator);
		if (start != -1 && end != -1) {
			start = start + 6;
			return filePath.substring(start, end + 1);
		}
		return "";
	}

	public static String getJavaPackagePath(String filePath) {
		int start = filePath.indexOf(File.separator + "java" + File.separator);
		int end = filePath.lastIndexOf(File.separator);
		if (start != -1 && end != -1) {
			start = start + 6;
			return filePath.substring(start, end + 1);
		}
		return "";
	}

}
